﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;

namespace GTAHookAdv
{
    /// <summary>
    /// g_Texture extends <see cref="GTA.Texture"/> class.
    /// it adds opacity and coloring features.It also garanty that picture size and position will allways be scalled whatever the player screen resolution.
    /// </summary>
    /// <remarks>g_Texture is mainly used in this API. e.g :for overlay controls</remarks>
    public class g_Texture : IDisposable
    {
        private GTA.Texture _texture;
        private GTA.Texture _textureOld;
        private readonly object _lockTexture = new object();

        /// <summary>
        /// Modders should not use this field, they'll call <see cref="Draw"/> method instead.
        ///Description: <see cref="GTA.Texture"/> to use with a <see cref="GTA.Graphics"/> to draw the picture (sprite).
        /// See the very important remarks about this field.
        /// </summary>
        /// <remarks>IMPORTANT !
        /// Never store this texture in a variable ! you'll have to call this field everytime you need the texture.
        /// This is because the texture can be disposed or remplaced by an other one at any time.
        /// So using this field is the only way to get the valid texture.</remarks>
        internal GTA.Texture Texture
        {
            get
            {
                if (_texture == null)
                    Texture = BuildTexture(ImageTransparent);
                return _texture;
            }
            private set
            {
                lock (_lockTexture)
                {
                    ReleaseGraphicObject();
                    _texture = value;
                }
            }
        }

        /// <summary>
        /// IMPORTANT : don't forget to call Dispose() when the texture is not needed anymore, it uses lots of ressources !!!
        /// </summary>
        /// <param name="img"> any bmp,png,jpeg,gif valid picture, usefull when used with Ressources</param>
        public g_Texture(System.Drawing.Image img)
        {
            if (img == null)
                throw new ArgumentNullException("img");
            ImageSource = img;
            Constructor();
        }

        /// <summary>
        /// Path to an existing picture, supported formats : bmp,jpeg,gif,png
        /// </summary>
        /// <param name="imgPath">full or partial path (related to GTA Root folder)</param>
        public g_Texture(String imgPath)
        {
            if (String.IsNullOrEmpty(imgPath))
                throw new ArgumentNullException("imgPath");
            if (!File.Exists(imgPath))
                throw new FileNotFoundException(imgPath);
            ImageSource = Image.FromFile(imgPath);
            Constructor();
        }

        private void Constructor()
        {
            this.OpacityChanged += OnOpacityChanged;
            this.Imagechanged += OnTextureNonValid;
            SizeToDisplayChanged += OnCoordinateChange;
            LocationChanged += OnCoordinateChange;
            BuildImageTransparent();
            Location = new Point(1, 1);
        }



        private void OnOpacityChanged(object sender, EventArgs e)
        {
            ReleaseGraphicObject();
            BuildImageTransparent();
            OnTextureNonValid(sender, e);
        }

        /// <summary>
        /// Source has change so texture will have to be rebuilt
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnTextureNonValid(object sender, EventArgs e)
        {
            Texture = BuildTexture(ImageTransparent);
        }

        private Image _imageSource;

        internal event EventHandler Imagechanged;

        internal Image ImageSource
        {
            get { return _imageSource; }
            set
            {
                _imageSource = value;
                BuildImageTransparent();
                _sizeImage = value.Size;
                _sizeToDisplay = value.Size;
                if (Imagechanged != null)
                    Imagechanged(this, EventArgs.Empty);
            }
        }

        private void BuildImageTransparent()
        {

            ImageTransparent = ImageOpacityInject(_imageSource, _opacity);
        }



        private Image _imageTransparent;

        internal Image ImageTransparent
        {
            get { return _imageTransparent; }
            private set
            {
                if (_imageTransparent != null)
                    _imageTransparent.Dispose();
                _imageTransparent = value;
            }
        }



        /// <include file='Overlay\\ControlOverlay.doc' path='IControlOverlay/Event[@name="OpacityChanged"]/*' />
        public event EventHandler OpacityChanged;

        private int _opacity = 100;

        /// <include file='Overlay\\ControlOverlay.doc' path='IControlOverlay/Field[@name="Opacity"]/*' />
        public int Opacity
        {
            get { return _opacity; }
            set
            {
                _opacity = value;
                if (OpacityChanged != null)
                    OpacityChanged(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// <see cref="Color"/> used to repaint the texture has changed
        /// </summary>
        public event EventHandler ColorChanged;

        System.Drawing.Color _color = Color.White;

        /// <summary>
        /// to alter image color, only usefull with picture only composed of fully Transparent and White Colors (see PictureBoxOverlay_Demo) for an example
        /// Default Color : White has no effect.
        /// </summary>
        public System.Drawing.Color Color
        {
            get { return _color; }
            set
            {
                _color = value;
                if (ColorChanged != null)
                    ColorChanged(this, EventArgs.Empty);
            }
        }

        Size _sizeImage;

        /// <summary>
        /// Size of the source image, see also SizeToDisplay field.
        /// There is no event for this field as it will always change when ImageChanged has been triggered
        /// </summary>
        public Size SizeImage
        {
            get { return _sizeImage; }

        }


        /// <summary>
        /// <see cref="SizeToDisplay"/> has been changed
        /// </summary>
        public event EventHandler SizeToDisplayChanged;

        SizeF _sizeToDisplay = SizeF.Empty;

        /// <summary>
        /// Size that the picture will have on screen.
        /// It is based on a 1920x1080 resolution, and will be scalled if player screen resolution is different.
        /// </summary>
        public SizeF SizeToDisplay
        {
            get { return _sizeToDisplay; }
            set
            {
                _sizeToDisplay = value;
                if (SizeToDisplayChanged != null)
                    SizeToDisplayChanged(this, EventArgs.Empty);
            }
        }

        private void OnCoordinateChange(object sender, EventArgs e)
        {
            FitToScreenResolution();
        }

        private void FitToScreenResolution()
        {
            _sizeDisplayInternal = GTAHookAdv.Screen.ResolutionHelper.ConvertToScr(_sizeToDisplay);
            PointF locationonScreen = new PointF(_location.X + _locationOffset.X, _location.Y + _locationOffset.Y);

            _locationInternal = GTAHookAdv.Screen.ResolutionHelper.ConvertToScr(locationonScreen); //Convert from 1920x1080 to current screen resolution.
        }


        SizeF _sizeDisplayInternal = SizeF.Empty;

        internal SizeF SizeDisplayInternal
        {
            get { return _sizeDisplayInternal; }

        }

        /// <include file='Overlay\\ControlOverlay.doc' path='IControlOverlay/Event[@name="LocationChanged"]/*' />
        public event EventHandler LocationChanged;

        private Point _location;

        /// <include file='Overlay\\ControlOverlay.doc' path='IControlOverlay/Field[@name="Location"]/*' />
        public Point Location
        {
            get { return _location; }
            set
            {
                _location = value;
                if (LocationChanged != null)
                    LocationChanged(this, EventArgs.Empty);
            }
        }

        private Point _locationOffset = new Point(0,0);

       
        /// <summary>
        /// Used by ControlOverlay when it's embended into a Parent Control.
        /// </summary>
        internal Point LocationOffset
        {
            get { return _locationOffset; }
            set
            {
                _locationOffset = value;
                FitToScreenResolution();
            }
        }

        private PointF _locationInternal;

        internal PointF LocationInternal
        {
            get { return _locationInternal; }

        }


        /// <summary>
        /// Use this in your <see cref="GTA.Script.PerFrameDrawing"/> callback.
        /// </summary>
        /// <param name="graphics"></param>
        public void Draw(GTA.Graphics graphics)
        {
            lock (_lockTexture)
            {
                graphics.DrawSprite(Texture, new RectangleF(LocationInternal, SizeDisplayInternal), Color);
            }
        }

        private Image ImageOpacityInject(Image img, int opacity)
        {
            if (img == null)
                throw new ArgumentNullException("img null so can't inject transparency into ImageOpacityInject Method");
            if (opacity < 0 || opacity > 100)
                throw new ArgumentOutOfRangeException("ImageOpacityInject Method : opacity must be between 0 and 100 (used as percent), received value is :" + opacity.ToString());
            float opa = ((float)opacity) / 100f;
            Bitmap bmp = new Bitmap(img.Width, img.Height); // Determining Width and Height of Source Image

            using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(bmp))
            {

                ColorMatrix colormatrix = new ColorMatrix();
                colormatrix.Matrix33 = opa;
                ImageAttributes imgAttribute = new ImageAttributes();
                imgAttribute.SetColorMatrix(colormatrix, ColorMatrixFlag.Default, ColorAdjustType.Default);

                graphics.DrawImage(img, new Rectangle(new Point(), new Size(img.Width, img.Height)), 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, imgAttribute);

                for (int i = 0; i < img.PropertyItems.Length; i++)
                {
                    bmp.SetPropertyItem(img.PropertyItems[i]);
                }
            }

            return bmp;
        }

        private GTA.Texture BuildTexture(Image img)
        {
            lock (_lockTexture)
            {
                byte[] imgRaw = GetImageRaw(img);

                return new GTA.Texture(imgRaw);
            }
        }

        /// <summary>
        /// as i had no choice, images must be converted before to build a texture, choosen format is png, fast to encode, some compression, good alpha rendering
        /// </summary>
        static System.Drawing.Imaging.ImageCodecInfo defaultImgEncoder = InitDefaultImgEncoder();

        private static ImageCodecInfo InitDefaultImgEncoder()
        {
            foreach (ImageCodecInfo item in ImageCodecInfo.GetImageEncoders())
            {
                if (item.FormatDescription == "PNG")
                    return item;
            }
            return null;
        }

        private byte[] GetImageRaw(Image img)
        {
            using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
            {
                System.Drawing.Imaging.Encoder myEncoder = System.Drawing.Imaging.Encoder.Quality;
                EncoderParameters prm = new EncoderParameters(1);
                EncoderParameter quality = new EncoderParameter(myEncoder, 100L);
                prm.Param[0] = quality;
                img.Save(stream, defaultImgEncoder, prm);
                return stream.ToArray();
            }
        }

        #region IDisposable Members
        /// <include file='Overlay\\ControlOverlay.doc' path='IControlOverlay/Field[@name="IsDisposed"]/*' />
        public bool IsDisposed
        {
            get { return _imageSource != null; }
        }

        /// <include file='Overlay\\ControlOverlay.doc' path='IControlOverlay/Method[@name="Dispose"]/*' />
        public void Dispose()
        {
            if (_imageSource != null)
                _imageSource.Dispose();
            _imageSource = null;
            ReleaseGraphicObject(); //To release _textureOld
            ReleaseGraphicObject(); //To release _texture


        }

        /// <summary>
        /// When you don't plan do display the texture for sometime, you shouuld call this methode, it release the texture ressource.
        /// Those Ressources will be rebuilt when calling Texture Field
        /// </summary>
        private void ReleaseGraphicObject()
        {
            lock (_lockTexture)
            {
                if (_textureOld != null)
                    _textureOld.Dispose();
                _textureOld = _texture;
                _texture = null;
            }
        }

        #endregion
    }
}
